package pri.damai.xiaowu.gateway.security.core;

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.web.server.ServerWebExchange;
import pri.damai.xiaowu.common.core.enums.ResultCommonEnum;
import pri.damai.xiaowu.common.core.vo.ResultVO;
import pri.damai.xiaowu.common.security.config.XiaoWuSecurityProperties;
import pri.damai.xiaowu.gateway.enums.GatewayResultEnum;
import reactor.core.publisher.Mono;

import java.util.*;

/**
 * 授权逻辑处理中心
 * @Desc
 * @Author DaMai
 * @Date 2021/3/23 15:26
 * 但行好事，莫问前程。
 */
@Slf4j
public class XiaoWuAuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {

    public static final String ROLE_URL_KEY = "userServer:role:url:urlId:";

    public static final String URL_SPLIT = "/";

    RedisTemplate<String, Object> redisTemplate;
    XiaoWuSecurityProperties securityProperties;

    public XiaoWuAuthorizationManager(RedisTemplate<String, Object> redisTemplate, XiaoWuSecurityProperties securityProperties) {
        this.redisTemplate = redisTemplate;
        this.securityProperties = securityProperties;

    }

    @Override
    public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, AuthorizationContext authorizationContext) {

        log.debug("路过 XiaoWuAuthorizationManager");
        ServerWebExchange exchange = authorizationContext.getExchange();
        ServerHttpRequest request = exchange.getRequest();
        // 预检放行
        if (request.getMethod() == HttpMethod.OPTIONS) {
            return Mono.just(new AuthorizationDecision(true));
        }

        String path = request.getURI().getPath();
        String realPath = this.getRealPath(path);

        boolean isNotAuthPass = securityProperties.isNotAuthPass(realPath);
        if (isNotAuthPass) {
            log.info("无需鉴权请求。{}",path);
            return Mono.just(new AuthorizationDecision(true));
        }

        // todo 从 redis 中取角色与url 对应关系，进行比对
//        log.debug("path:{},realPath:{}", path, realPath);

        //认证通过且角色匹配的用户可访问当前路径
        return authentication
                .filter(Authentication::isAuthenticated)
                .flatMapIterable(auth -> {
                    log.debug(auth.getAuthorities().toString());
                    return auth.getAuthorities();
                })
                .map(GrantedAuthority::getAuthority)
                .any(s -> {
//                    String roleUrlTokenKey = this.getRoleUrlTokenKey(s);
//                    Boolean hasKey = redisTemplate.opsForHash().hasKey(roleUrlTokenKey, realPath);
//                    log.debug("角色 {}, 有 url:{}", roleUrlTokenKey, hasKey);
                    return true;
                })
                .map(AuthorizationDecision::new)
                .defaultIfEmpty(new AuthorizationDecision(false));
    }

    @Override
    public Mono<Void> verify(Mono<Authentication> authentication, AuthorizationContext object) {

        log.info("verfy.{}",authentication);

        return check(authentication, object)
                .filter(AuthorizationDecision::isGranted)
                .switchIfEmpty(Mono.defer(() -> {
                    String body = JSONObject.toJSONString(ResultVO.error(GatewayResultEnum.PERMISSION_DENIED));
                    return Mono.error(new AccessDeniedException(body));
                }))
                .flatMap(d -> Mono.empty());
    }

    private String getRoleUrlTokenKey(String roleId) {
        return ROLE_URL_KEY + roleId;
    }

    private String getRealPath(String path) {
        String[] split = path.split(URL_SPLIT, 3);
        return URL_SPLIT + split[split.length - 1];
    }

    public static void main(String[] args) {
        String[] split = "/auth/test/test".split("/", 3);
        System.out.println(Arrays.toString(split));
    }

}
